﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializedItem.cs" company="Marcelo Roca">
//   Copyright (c) 2011 Marcelo Roca. All rights reserved.
// </copyright>
// <author name="Marcelo Roca" email="marce@marceloroca.com"/>
// <license>
//   This program is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
// 
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program.  If not, see http://www.gnu.org/licenses/.
// </license>
// --------------------------------------------------------------------------------------------------------------------

namespace GameKits.Unity.SmartInspector.Editor.Serializer
{

    using System;
    using System.Collections;
    using System.Reflection;

    using UnityEngine;

    /// <summary>
    /// Esta clase es el item serializado
    /// </summary>
    internal class SerializedItem
    {
        /// <summary>
        /// Se utiliza para identificar si es una propiedad, variable, método o una custom
        /// class
        /// </summary>
        private readonly ItemTypes memberType;

        /// <summary>
        /// Almacena la información de una propiedad cuando el tipo es MemberTypes.Property
        /// </summary>
        /// <remarks>
        /// Es null cuando el tipo no es del tipo MemberTypes.Property
        /// </remarks>
        private readonly PropertyInfo propertyInfo;

        /// <summary>
        /// Almacena la información cuando el tipo es MemberTypes.Field
        /// </summary>
        private readonly FieldInfo fieldInfo;

        /// <summary>
        /// Almacena la información cuando el tipo es MemberTypes.Method
        /// </summary>
        private readonly MethodInfo methodInfo;

        /// <summary>
        /// El indice del arreglo donde se produce la edición
        /// </summary>
        private readonly int arrayIndex;

        /// <summary>
        /// Es el arreglo que se está editando
        /// </summary>
        private readonly Array array;

        /// <summary>
        /// the target object
        /// </summary>
        private readonly object target;

        /// <summary>
        /// Initializes a new instance of the <see cref="SerializedItem"/> class.
        /// </summary>
        /// <param name="memberInfo">
        /// The property info.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        public SerializedItem(MemberInfo memberInfo, object target, BrowsableAttribute attribute)
            : this(target, attribute, memberInfo.Name, GetSystemType(memberInfo), GetItemType(memberInfo))
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Field:
                    this.fieldInfo = (FieldInfo)memberInfo;
                    break;

                case MemberTypes.Property:
                    this.propertyInfo = (PropertyInfo)memberInfo;
                    break;

                case MemberTypes.Method:
                    this.methodInfo = (MethodInfo)memberInfo;
                    this.IntTool = int.MinValue;
                    this.target = target;
                    this.Name = this.methodInfo.Name;
                    this.Label = GenLabel(this.methodInfo.Name, attribute);
                    this.memberType = ItemTypes.Method;

                    if (attribute != null)
                    {
                        this.Attribute = attribute;
                    }

                    break;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SerializedItem"/> class.
        /// </summary>
        /// <param name="array">
        /// The array.
        /// </param>
        /// <param name="arrayIndex">
        /// The array index.
        /// </param>
        /// <param name="name">
        /// The name of tehe element.
        /// </param>
        /// <param name="type">
        /// The type of the element.
        /// </param>
        public SerializedItem(Array array, int arrayIndex, string name, Type type)
            : this(null, null, name, type, ItemTypes.ArrayItem)
        {
            this.arrayIndex = arrayIndex;
            this.array = array;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SerializedItem"/> class.
        /// </summary>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <param name="name">
        /// The name (caption).
        /// </param>
        /// <param name="systemType">
        /// The type of the control.
        /// </param>
        /// <param name="itemType">
        /// The item Type.
        /// </param>
        private SerializedItem(object target, BrowsableAttribute attribute, string name, Type systemType, ItemTypes itemType)
        {
            this.IntTool = int.MinValue;
            this.target = target;
            this.Name = name;
            this.Label = GenLabel(name, attribute);
            this.SystemType = systemType;
            this.memberType = itemType;

            if (attribute != null)
            {
                this.Attribute = attribute;
            }

            if (this.SystemType == null)
            {
                if (this.memberType != ItemTypes.Method)
                {
                    throw new ArgumentNullException("systemType");
                }
            }
            else if (this.SystemType.IsArray)
            {
                this.ValueType = ValueTypes.Array;
            }
            else if (this.SystemType == typeof(ArrayList))
            {
                this.ValueType = ValueTypes.ArrayList;
            }
            else if (this.SystemType == typeof(string))
            {
                if (this.Attribute != null && this.Attribute is BrowsableTagAttribute)
                {
                    this.ValueType = ValueTypes.TagString;
                }
                else if (this.Attribute != null && this.Attribute is BrowsablePasswordAttribute)
                {
                    this.ValueType = ValueTypes.PasswordString;
                }
                else
                {
                    this.ValueType = ValueTypes.String;
                }
            }
            else if (this.SystemType == typeof(Vector2))
            {
                this.ValueType = ValueTypes.Vector2;
            }
            else if (this.SystemType == typeof(Vector3))
            {
                this.ValueType = ValueTypes.Vector3;
            }
            else if (this.SystemType == typeof(Vector4))
            {
                this.ValueType = ValueTypes.Vector4;
            }
            else if (this.SystemType == typeof(Rect))
            {
                this.ValueType = ValueTypes.Rect;
            }
            else if (this.SystemType == typeof(AnimationCurve))
            {
                this.ValueType = ValueTypes.AnimationCurve;
            }
            else if (this.SystemType == typeof(float))
            {
                this.ValueType = ValueTypes.Float;
            }
            else if (this.SystemType == typeof(int))
            {
                if (this.Attribute != null && this.Attribute is BrowsableLayerAttribute)
                {
                    this.ValueType = ValueTypes.LayerInt;
                }
                else
                {
                    this.ValueType = ValueTypes.Int;
                }
            }
            else if (this.SystemType == typeof(bool))
            {
                this.ValueType = ValueTypes.Bool;
            }
            else if (this.SystemType.IsEnum)
            {
                this.ValueType = ValueTypes.Enum;
            }
            else if (this.SystemType == typeof(Color))
            {
                this.ValueType = ValueTypes.Color;
            }
            else if (this.SystemType.IsClass)
            {
                if (typeof(UnityEngine.Object).IsAssignableFrom(this.SystemType))
                {
                    this.ValueType = ValueTypes.UnityEngineObject;
                }
                else if (this.SystemType.GetType().IsSerializable)
                {
                    this.ValueType = ValueTypes.SerializedClass;
                }
                else
                {
                    this.ValueType = ValueTypes.Class;
                }
            }
        }

        /// <summary>
        /// El tipo de item
        /// </summary>
        public enum ItemTypes
        {
            Unknown,
            Field,
            Method,
            Property,
            ArrayItem,
        }

        /// <summary>
        /// El tipo de valor
        /// </summary>
        public enum ValueTypes
        {
            Array,
            ArrayList,
            String,
            PasswordString,
            TagString,
            Vector2,
            Vector3,
            Vector4,
            Rect,
            AnimationCurve,
            Float,
            Int,
            LayerInt,
            Bool,
            Color,
            Class,
            UnityEngineObject,
            SerializedClass,
            Enum
        }

        /// <summary>
        /// Gets or sets Label.
        /// </summary>
        public GUIContent Label { get; set; }

        /// <summary>
        /// Gets MemberType.
        /// </summary>
        public ItemTypes MemberType
        {
            get
            {
                return this.memberType;
            }
        }

        /// <summary>
        /// Gets SystemType.
        /// </summary>
        public Type SystemType { get; private set; }

        /// <summary>
        /// Gets ValueType.
        /// </summary>
        public ValueTypes ValueType { get; private set; }

        /// <summary>
        /// Gets Name.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets Attribute.
        /// </summary>
        public BrowsableAttribute Attribute { get; private set; }

        /// <summary>
        /// Gets or sets IntTool.
        /// </summary>
        public int IntTool { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether BoolTool.
        /// </summary>
        public bool BoolTool { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether Disabled.
        /// </summary>
        public bool Disabled { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether Readonly.
        /// </summary>
        public bool Readonly { get; set; }

        /// <summary>
        /// Return the valua of a field supported by a given object.
        /// </summary>
        /// <returns>
        /// The value of the field or property
        /// </returns>
        public object GetValue()
        {
            switch (this.memberType)
            {
                case ItemTypes.Field:
                    return this.fieldInfo.GetValue(this.target);

                case ItemTypes.Property:
                    return this.propertyInfo.GetValue(this.target, null);

                case ItemTypes.ArrayItem:
                    return this.array.GetValue(this.arrayIndex);
            }

            return null;
        }

        /// <summary>
        /// Set the value of the field/property supported by the given object.
        /// </summary>
        /// <param name="value">
        /// The value to set.
        /// </param>
        public void SetValue(object value)
        {
            switch (this.memberType)
            {
                case ItemTypes.Field:
                    this.fieldInfo.SetValue(this.target, value);
                    break;

                case ItemTypes.Property:
                    if (this.propertyInfo.CanWrite)
                    {
                        this.propertyInfo.SetValue(this.target, value, null);
                    }

                    break;

                case ItemTypes.ArrayItem:
                    this.array.SetValue(value, this.arrayIndex);
                    break;
            }
        }

        /// <summary>
        /// Invoca al methodo
        /// </summary>
        public void Invoke()
        {
            if (this.methodInfo == null || this.target == null)
            {
                return;
            }

            this.methodInfo.Invoke(this.target, null);
        }

        /// <summary>
        /// Obtiene el tipo de Item desde un memberInfo
        /// </summary>
        /// <param name="memberInfo">
        /// The member info.
        /// </param>
        /// <returns>
        /// El tipo de Item que es
        /// </returns>
        private static ItemTypes GetItemType(MemberInfo memberInfo)
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Property:
                    return ItemTypes.Property;

                case MemberTypes.Field:
                    return ItemTypes.Field;

                case MemberTypes.Method:
                    return ItemTypes.Method;

                default:
                    return ItemTypes.Unknown;
            }
        }

        /// <summary>
        /// Del MemberType obtiene el tipo de dato (Type)
        /// </summary>
        /// <param name="memberInfo">
        /// The member info.
        /// </param>
        /// <returns>
        /// el tipo del campo (System.Type)
        /// </returns>
        private static Type GetSystemType(MemberInfo memberInfo)
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Property:
                    return ((PropertyInfo)memberInfo).PropertyType;

                case MemberTypes.Field:
                    return ((FieldInfo)memberInfo).FieldType;

                default:
                    return null;
            }
        }

        /// <summary>
        /// Geenra una etiqueta GUIContent
        /// </summary>
        /// <param name="name">
        /// The name (caption).
        /// </param>
        /// <param name="attribute">
        /// The attribute .
        /// </param>
        /// <returns>
        /// La etiqueta del item serializado
        /// </returns>
        private static GUIContent GenLabel(string name, BrowsableAttribute attribute)
        {
            if (attribute == null)
            {
                return new GUIContent(name);
            }

            return
                new GUIContent(
                    (attribute.ReadOnly || attribute.Disabled
                         ? "[" + ((attribute.ReadOnly ? "r" : string.Empty) + (attribute.Disabled ? "d" : string.Empty)) + "]"
                         : string.Empty) + (string.IsNullOrEmpty(attribute.Caption) ? name : attribute.Caption),
                    attribute.Tooltip);
        }
    }
}
